﻿using System;
using System.Globalization;
using System.ComponentModel;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel.Elements.CWNotation
{
    /// <summary>
    /// Adapts a <see cref="Model.Elements.CWNotation.Accidental"/> instance to a Windows Presentation Foundation view.
    /// </summary>
    public class Accidental : ViewModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="Accidental"/> instance.
        /// </summary>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="Note"/>
        /// </para>
        /// <para>
        /// The element that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="adaptedElement">
        /// <para>
        /// Type: <see cref="Model.Elements.CWNotation.Accidental"/>
        /// </para>
        /// <para>
        /// The adapted model element instance.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="adaptedElement"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="NullReferenceException">
        /// <paramref name="owner"/> is <see langword="null"/>.
        /// The values of the <see cref="ViewModelElement.UndoStack"/> and <see cref="ViewModelElement.RedoStack"/> properties
        /// are passed directly to the base class (<see cref="ViewModelElement"/>) constructor. Thus, there is not
        /// an occasion to react with a more appropriate exception.
        /// </exception>
        public Accidental(Note owner, Model.Elements.CWNotation.Accidental adaptedElement)
            : base(adaptedElement, owner, owner.UndoStack, owner.RedoStack)
        {
            RegisterToOwnerPropertyChanged<Note>();
        }

        #endregion

        #region ViewModelElement Members

        /// <inheritdoc/>
        public override string ElementNameKey
        {
            get { return elementNameKey; }
        }

        /// <inheritdoc/>
        public override void Dispose()
        {
            UnregisterFromOwnerPropertyChanged<Note>();
            base.Dispose();
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the value of some of the owner's property has changed.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the modified property is <see cref="Note.HeadPosition"/> the handler raises the <see cref="ViewModelElement.PropertyChanged"/>
        /// event for the <see cref="Position"/> property.<br/>
        /// If the modified property is <see cref="Note.VerticalPosition"/> the handler raises the <see cref="ViewModelElement.PropertyChanged"/>
        /// event for the <see cref="VerticalPosition"/> property.
        /// </remarks>
        /// <param name="owner">Object that has sent the event</param>
        /// <param name="e">Event data</param>
        protected override void Owner_PropertyChanged(ViewModelElement owner, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "HeadPosition")
                RaisePropertyChanged("Position");
            else if (e.PropertyName == "VerticalPosition")
                RaisePropertyChanged("VerticalPosition");
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Creates an undoable command for setting the <see cref="Type"/> of the accidental.
        /// </summary>
        /// <param name="type">
        /// <para>
        /// Type: <see cref="AccidentalType"/>
        /// </para>
        /// <para>
        /// The new accidental type.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="UndoableCommandBase"/>
        /// </para>
        /// <para>
        /// The created command ready for execution.
        /// </para>
        /// </returns>
        public UndoableCommandBase CreateSetTypeCommand(AccidentalType type)
        {
            return new SetAccidentalCommand(UndoStack, RedoStack)
            {
                Note = FindOwner<Note>(),
                Accidental = type
            };
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the effective type size of the symbol.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The effective type size (in device independent pixels) of the symbol.
        /// </para>
        /// </value>
        public double EffectiveTypeSize
        {
            get
            {
                return FindOwner<Note>().EffectiveTypeSize;
            }
        }

        /// <summary>
        /// Gets the position of the accidental.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The distance (in device independent pixels) between the accidental
        /// and the left section's edge.
        /// </para>
        /// </value>
        public double Position
        {
            get { return FindOwner<Chord>().Position + (double)LengthConverter.Convert(((Model.Elements.CWNotation.Accidental)AdaptedElement).Position, typeof(double), null, CultureInfo.CurrentCulture); }
        }

        /// <summary>
        /// Gets or sets the type of the accidental.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="AccidentalType"/>
        /// </para>
        /// <para>
        /// The type of the accidental.
        /// </para>
        /// </value>
        public AccidentalType Type
        {
            get
            {
                var modelType = GetPropertyValue<Model.Elements.CWNotation.AccidentalType>("Type");

                return (AccidentalType)Enum.Parse(typeof(AccidentalType), modelType.ToString());
            }
            set
            {
                if (UnsavedChanges.ContainsKey("Type"))
                {
                    var cmd = (SetterCommand<Model.Elements.CWNotation.AccidentalType>)UnsavedChanges["Type"];
                    cmd.Value = (Model.Elements.CWNotation.AccidentalType)Enum.Parse(typeof(Model.Elements.CWNotation.AccidentalType), value.ToString());
                }
                else
                    UnsavedChanges["Type"] = CreateSetTypeCommand(value);

                RaisePropertyChanged("Type");
            }
        }

        /// <summary>
        /// Gets the vertical position of the accidental.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Double"/>
        /// </para>
        /// <para>
        /// The distance (in device independent pixels) between the accidental
        /// and the top section's edge.
        /// </para>
        /// </value>
        public double VerticalPosition
        {
            get { return FindOwner<Note>().VerticalPosition; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds a user-friendly name of this element.
        /// </summary>
        private const string elementNameKey = "ViewModel.ElementNames.Accidental";
        
        #endregion
    }

    /// <summary>
    /// Describes the type of an accidental.
    /// </summary>
    public enum AccidentalType
    {
        /// <summary>
        /// No accidental.
        /// </summary>
        None,
        /// <summary>
        /// Neutral (cancels a flat or a sharp).
        /// </summary>
        Neutral,
        /// <summary>
        /// Double-neutral (cancels a double-flat or a double-sharp).
        /// </summary>
        DoubleNeutral,
        /// <summary>
        /// Double-flat (lowers the tone by two semitones).
        /// </summary>
        DoubleFlat,
        /// <summary>
        /// Flat (lowers the tone by one semitone).
        /// </summary>
        Flat,
        /// <summary>
        /// Sharp (raises the tone by one semitone).
        /// </summary>
        Sharp,
        /// <summary>
        /// Double-sharp (raises the tone by two semitones).
        /// </summary>
        DoubleSharp
    }
}
